ブログ

割とコンピュータよりの情報をお届けします。

Windows Forms

OxyPlotにHeatMapSeriesというのがある

OxyPlotにHeatMapSeriesというのがあるというのを知った.これまでは,Bitmapで自分で描いていた.

Bitmapで自分で描くより楽だ.例えばここのようなサンプルが良い.
下は参考ページのやり方・名前の取り方をほぼそのまま参照している.Windows Forms用に少し手直しが入っているのと,linearColorAxis1.Palette = OxyPalettes.Gray(10);を追加しているところくらいが異なる.(参考ではRainbowになっている?)
縦横に正弦波が0.5 秒おきに流れていく(感じ).

using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Series;
using OxyPlot.WindowsForms;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace HeatmapTest
{
    private OxyPlot.WindowsForms.PlotView plotView1;
        private PlotModel plotModelSpectrogram = new PlotModel();
        public HeatMapSeries heatMapSeries1 = new HeatMapSeries();
        private Double[,] Data = new Double[240, 250];
        private double t = 0;

        private System.Windows.Forms.Timer timer1;

        public Form1()
        {
            InitializeComponent();

            this.plotView1 = new OxyPlot.WindowsForms.PlotView();

            this.plotView1.Dock = System.Windows.Forms.DockStyle.Fill;
            this.plotView1.Location = new System.Drawing.Point(0, 0);
            this.plotView1.Name = "plotView1";
            this.plotView1.PanCursor = System.Windows.Forms.Cursors.Hand;
            this.plotView1.Size = new System.Drawing.Size(633, 362);
            this.plotView1.TabIndex = 0;
            this.plotView1.Text = "plotView1";
            this.plotView1.ZoomHorizontalCursor = System.Windows.Forms.Cursors.SizeWE;
            this.plotView1.ZoomRectangleCursor = System.Windows.Forms.Cursors.SizeNWSE;
            this.plotView1.ZoomVerticalCursor = System.Windows.Forms.Cursors.SizeNS;

            this.Controls.Add(this.plotView1);

            plotModelSpectrogram.Title = "TEST";

            var linearColorAxis1 = new LinearColorAxis();
            linearColorAxis1.Palette = OxyPalettes.Gray(10);
            linearColorAxis1.Position = AxisPosition.Right;
            linearColorAxis1.Minimum = 0.0;
            linearColorAxis1.Maximum = 100;
            plotModelSpectrogram.Axes.Add(linearColorAxis1);


            var linearAxis1 = new LinearAxis();
            linearAxis1.Position = AxisPosition.Bottom;
            linearAxis1.IsAxisVisible = false;
            linearAxis1.IsZoomEnabled = false;
            plotModelSpectrogram.Axes.Add(linearAxis1);

            // Dummy
            var linearAxis_a = new LinearAxis();
            linearAxis_a.Position = AxisPosition.Bottom;
            linearAxis_a.Minimum = 0.0;
            linearAxis_a.Maximum = 120.0;
            linearAxis_a.Title = "Time s";
            linearAxis_a.IsZoomEnabled = false;
            plotModelSpectrogram.Axes.Add(linearAxis_a);

            var linearAxis2 = new LinearAxis();
            linearAxis2.Position = AxisPosition.Left;
            linearAxis2.IsAxisVisible = false;
            linearAxis2.IsZoomEnabled = false;
            plotModelSpectrogram.Axes.Add(linearAxis2);

            // Dummy
            var linearAxis_b = new LinearAxis();
            linearAxis_b.Position = AxisPosition.Left;
            linearAxis_b.Minimum = 0.0;
            linearAxis_b.Maximum = 1000.0;
            linearAxis_b.Title = "Frequency kHz";
            linearAxis_b.IsZoomEnabled = false;
            plotModelSpectrogram.Axes.Add(linearAxis_b);


            heatMapSeries1.Data = new double[240, 250];
            heatMapSeries1.X0 = 0.0;
            heatMapSeries1.X1 = 100.0;
            heatMapSeries1.Y0 = 0.0;
            heatMapSeries1.Y1 = 1000.0;

            plotModelSpectrogram.Series.Add(heatMapSeries1);
            this.plotView1.Model = plotModelSpectrogram;

            this.timer1 = new Timer();
            this.timer1.Interval = 500;
            this.timer1.Enabled = true;
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);

        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            PlotSpectrogram();
        }

        private void AddLastData(Double[] data)
        {
            for (int i = 0; i < Data.GetLength(0) - 1; i++)
            {
                for (int j = 0; j < Data.GetLength(1); j++)
                {
                    Data[i, j] = Data[i + 1, j];
                }
            }
            for (int j = 0; j < Data.GetLength(1); j++)
            {
                Data[Data.GetLength(0) - 1, j] = data[j];
            }

            var pldata = new double[240, 250];
            Array.Copy(Data, pldata, Data.Length);
            heatMapSeries1.Data = pldata;
        }

        public void PlotSpectrogram()
        {
            Double[] data = new Double[Data.GetLength(1)];
            for (int i = 0; i < data.Length; i++)
            {
                data[i] = (50.0 + 20.0 * Math.Sin(0.2 * i) + 20.0 * Math.Sin(t));

            }
            t += 0.5;
            AddLastData(data);
            plotModelSpectrogram.InvalidatePlot(true);
        }
    }
}

≫ 続きを読む

2021/06/07 コンピュータ   TakeMe
タグ:Windows Forms

.NET Frameworkとネイティブdllが混ざったときの不具合追及

.NETでDllImportを使っていると例外が発生しないで突然「××は動作を停止しました。」と出てしまうことがある。最近この場面に遭遇した。

「××は動作を停止しました。」と出たときにWindowsコントロールの「信頼性の履歴の表示」に,APPCRASHというイベント名で記録が残る。
これは便利なのだが,例外の種類が0xc0000374になっていて発生頻度が割と低い場合には非常にデバッグが難しい。
これはヒープ破損らしい。
そもそも,.NET Framework 4.0からAccessViolationExceptionを検知できなくなっているらしい。そのため,例外をキャッチしてクラッシュログを残そうなどということができない。app.configに<legacyCorruptedStateExceptionsPolicy enabled="true"/>を追加することでこれは回避できる。
しかし,それだけでは見逃す。0xc0000374はヒープ破壊を起こした時に起きるというより,壊れているのを検知したときに起きる(実害が発生しそうなタイミングで起きる)ので問題のあるコードはもっと前に実行されていることがある。

そこで,Windows SDKに含まれるDebugging Tools for WindowsgflagsでPage Heapを有効にして置きexe.configの<runtime>要素内に<legacyCorruptedStateExceptionsPolicy enabled="true"/>を追加して,ヒープ破壊っぽいことが起きたらAccessViolationExceptionが上がるようにして例外をキャッチしてクラッシュログを残す。(といいらしい)

Page Heapは非常に重いが,例外発生の確実性が大幅に増す。

≫ 続きを読む

2021/04/09 コンピュータ   TakeMe
タグ:WPF , Windows Forms

WebView2ランタイムで音声読み上げ Vol. 2

WebView2なら音声読み上げを入れることができる.SAPIの代わりに使いたい場合には動的にJavaScriptを実行するようにならないといけないな。と思って修正.

SAPIの代わりに使う場合には,WebView2.CoreWebView2.ExecuteScriptAsyncを使えばよい.
例えとして下にサンプルを示す.
ただし,この例では毎回var synthes = new SpeechSynthesisUtterance()とやっているので注意して.オーバーヘッドが大きい.
もともとWindows 10の場合にはすでにSAPIがあるので読み上げ機能を使うだけでやるのは意味はない.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            string path = Directory.GetCurrentDirectory();

            path = path + @"\\index.html";

            this.webView21.Source = new Uri(path);
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            string text =
                "var synthes = new SpeechSynthesisUtterance('こんにちは');" +
                "speechSynthesis.speak(synthes);";
            await this.webView21.CoreWebView2.ExecuteScriptAsync(text);
        }
    }
}


WebView2.CoreWebView2.ExecuteScriptAsyncがつかえるのはページを読み終わって表示し終わってからだと思う.その前に実行すると例外が発生すると思う.

SAPIだけでやる場合には以下のように行うのです.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{
    class Program
    {
        static void Main(string[] args)
        {
            SpeechLib.SpVoice sp = new SpeechLib.SpVoice();
            sp.Speak("アレクサ。今なんじ");
        }
    }
}

≫ 続きを読む

2021/03/27 コンピュータ   TakeMe
タグ:Windows Forms

ZXing.netでQRコード遊び

ちょっと古いネタだけどQRコードにどれだけ文字を詰められるのかを調べていたら,nugetでないかなーと調べたらQRCoderというのが先に出てきて試していたが,オーバーフローしても何も出てこない。

Zxing.Netがの方が作り込まれている。これが一番簡単な例。

だいぶん前にQRコードを使うために使用したけど,忘れていた。そのため最初にQRCoderを使っていたがなかなか使い勝手が悪い。通常の長さの文字列なら問題ないが長くなってくるとスマホで読まなくなる現象が発生。
結局前に使ったのはどんなバーコード作成ライブラリだったのかを調べてZXingだった。そして参考ページを探した。
ZXing.NETをnugetで入れて,例えば,以下のように使用する。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using ZXing;

namespace ZxingTEST
{
    public partial class Form1 : Form
    {
        private BarcodeWriter qrcode;
        public Form1()
        {
            InitializeComponent();

            qrcode = new BarcodeWriter
            {
                Format = BarcodeFormat.QR_CODE,
                Options = new ZXing.QrCode.QrCodeEncodingOptions
                {
                    ErrorCorrection = ZXing.QrCode.Internal.ErrorCorrectionLevel.Q,
                    CharacterSet = "UTF-8",
                    Height = 320,
                    Width = 320,
                    Margin = 2
                }
            };
        }

        private void button1_Click(object sender, EventArgs e)
        {
            pictureBox1.Image = qrcode.Write(this.textBox1.Text);
        }
    }
}

QRCoderではオーバーフローがどうなっているのか不明だが,ZXing.NETでは例外が返るようになっていた。正しく伝達できるかは、読み取り側のソフト次第だが日本語も書き込みできる。

≫ 続きを読む

2021/03/21 コンピュータ   TakeMe
タグ:Windows Forms

Windows Forms用のVisual Studio データソース構成ウィザード

Windows Formsのアプリケーションを作っていて,データバインディングを使ってみたが,Visual Studio データソース構成ウィザードはAny CPU構成しか対応していないかもしれない。

ある日,Windows Formsのアプリケーションを作っていて,データバインディングを使ってみた。
x64用限定にしようと思って,構成マネージャでx64構成を作成してプロジェクトのプロパティを変更していた。

Visual Studio データソース構成ウィザードでx64構成ではオブジェクトが一覧に出てこない。

やり方を間違えたかと思ってうろうろしていたが,結局ウィザードがみているのはAny CPU設定の時の標準の出力パス bin\Debugやbin\Release ディレクトリ。
そこにオブジェクトがないといけないようだ.
(普通そんなこと気付くかな)

対象プラットフォームだけをx64にしている場合には出力パスは変化しないので問題にならない(はず)。構成マネージャでx64を新規作成してそれを選択した状態でビルドすると時々起こる.

≫ 続きを読む

2021/02/27 コンピュータ   TakeMe
タグ:Windows Forms

Python for .NETの新しい話 Vol. 2

Pythonでsgolayフィルタを使ってみた..NETでもオープンソースのコードがないかなと思っていたのですが,なかなか見つからなかったのでscipyのコードを使うことにしてみた.

自分で探していたけど「SciPy で Savitzky-Golay フィルタ」という参考にmodeなるものも指定できることが分かった.SciPyの方が簡単かな

 

やっぱり簡単.ただし,Scipyはかなりストレージを使う.これを小さくできたらよいのだが...

C#側のコードは次のような感じにしてみた.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Python.Runtime;

namespace SgolaySample
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();

            System.Windows.Forms.DataVisualization.Charting.ChartArea chartArea1 = new System.Windows.Forms.DataVisualization.Charting.ChartArea();
            System.Windows.Forms.DataVisualization.Charting.Legend legend1 = new System.Windows.Forms.DataVisualization.Charting.Legend();
            System.Windows.Forms.DataVisualization.Charting.Series series1 = new System.Windows.Forms.DataVisualization.Charting.Series();
            System.Windows.Forms.DataVisualization.Charting.Series series2 = new System.Windows.Forms.DataVisualization.Charting.Series();
            this.chart1 = new System.Windows.Forms.DataVisualization.Charting.Chart();
            // 
            // chart1
            // 
            chartArea1.Name = "ChartArea1";
            this.chart1.ChartAreas.Add(chartArea1);
            legend1.Name = "Legend1";
            this.chart1.Legends.Add(legend1);
            this.chart1.Location = new System.Drawing.Point(12, 12);
            this.chart1.Name = "chart1";
            series1.ChartArea = "ChartArea1";
            series1.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Point;
            series1.Legend = "Legend1";
            series1.Name = "Series1";
            series2.ChartArea = "ChartArea1";
            series2.ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Line;
            series2.Legend = "Legend1";
            series2.Name = "Series2";
            this.chart1.Series.Add(series1);
            this.chart1.Series.Add(series2);
            this.chart1.Size = new System.Drawing.Size(446, 206);
            this.chart1.TabIndex = 0;
            this.chart1.Text = "chart1";


            this.Controls.Add(this.chart1);

            string strPath = Environment.GetEnvironmentVariable("PATH");

            string appDir = Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName + @"\python";

            Environment.SetEnvironmentVariable("PATH", Path.PathSeparator + appDir, EnvironmentVariableTarget.Process);


            List<double> x = new List<double>(); // Python listへの変換予定
            List<double> y = new List<double>(); // Python listへの変換予定

            Random random = new Random(0);
            for (int i = 0; i < 400; i++)
            {
                x.Add(0.01 * i);
                y.Add(Math.Sin(2.0 * Math.PI * x[i] / 1.0) + random.NextDouble());
            }

            using (Py.GIL())
            {
                dynamic sys = Py.Import("sys");
                // sample.pyを置くフォルダをパスに追加
                sys.path.append(Directory.GetParent(Assembly.GetExecutingAssembly().Location).FullName);
            }

            using (Py.GIL()) 
            {
                dynamic sample = Py.Import("sample");
                dynamic ya = sample.filterA(y); // numpy.arrayで返ってくる予定

                for (int i = 0; i < x.Count; i++) {
                    chart1.Series[0].Points.AddXY(x[i], y[i]);
                    chart1.Series[1].Points.AddXY(x[i], ((double[])ya)[i]);
                }
            }
        }
    }
}

sample.pyの例

import numpy as np
from scipy import signal

def filterA(y):
    ya = signal.savgol_filter(y, 101, 5);
    return ya;

≫ 続きを読む

2021/02/22 コンピュータ   TakeMe

OpenTKの参考ページをいろいろ探していた

OpenTKの参考ページを探していた。かなりたくさんの情報が盛り込まれているページは鳩でもわかるC#
OpenTKだけの参考本はほぼないかもしれない。

Windows FormsやWPFで簡単に GPUを使った描画をやらせたいと思っていた時に,OpenTKの記事が参考になった.

記事では,手動でOpenTKのライブラリを追加しているが,これはopentkのライブラリをインストールさせるところから始まっているため。

nugetパッケージをとってくるのが一番簡単かな。nugetパッケージで入れてやるとビルド時も実行に必要なファイルはターゲットディレクトリに書き出してくれる。ツールボックスからGLControlがドラッグアンドドロップできる状態にもできる。

nugetでは「OpenTK」ではなく「OpenTK.GLControl」を探して選択してインストールして,OpenTKが合わせてインストールされる状態に持っていく方がよさそう。
しばらく参考を見てみるところ。

-- 
参考ページ

 

≫ 続きを読む

2021/01/31 コンピュータ   TakeMe

Windows Embedded でのMessageBoxの扱いの注意

Windows Embedded Standard向けのアプリを開発していたら,.NETで64bitでも32bitでもどちらでもアプリが動かしるようにしていたところ,32bitでの動作では普通にMessageBoxが開くのに...

組み込み用のWindowsではWindows標準のMessageBoxの動作を変更することができる.すべてOKを応答させてダイアログを表示させないようにするのだ.

.NET Frameworkで開発したアプリで特に64bitで動かしたときにできるらしい.
この機能は開発時にMessageBoxを出させておいて,展開時にはMessageBoxを抑制するというという使い方になる.
知らなかったのでダイアログが出ない理由を一週間かけて探した.

≫ 続きを読む

2019/10/19 コンピュータ   TakeMe
タグ:WPF , Windows Forms

Windows FormsのDockプロパティFillはダブルバッファリングと相性が悪い

今回は,Windows Formsのアプリケーションを作成していた.
DataBindingを設定して,更新してみていたが どうもちらつく.原因はDockプロパティだった.
「Windows FormsのDockプロパティFillはダブルバッファリングと相性が悪い」ではなく「Windows FormsのDockプロパティFillはダブルバッファリングと相性が悪いかったかも」が正しいのかもしれない.実行環境を新しくしていくと問題が消えていく...

そもそも,本題の更新時のちらつきについて話を進める前に,
WPFのデータバインディングの場合には,バックグラウンドスレッドで更新しても普通に更新がかかるのだが,Windows Formsの場合にはUIスレッドで意図的にプロパティ変更イベントを出さないといけないようだ.
さて本題だが,Windows FormsのLabelは標準でダブルバッファリングが有効になったコントロールである.
しかし,DockプロパティをFillにしているとちらつく(ことがある).
ただし,Windows 10では問題ないように見える.例えば以下のようなコードはWindows 10で問題ない(っぽい)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        private Timer timer = new Timer();
        private Label label = new Label();
        private Label label2 = new Label();
        private TableLayoutPanel tableLayoutPanel = new TableLayoutPanel();
        private Int64 count = 0;

        private ViewModel vm = new ViewModel();

        public Form1()
        {
            InitializeComponent();

            this.Controls.Add(tableLayoutPanel);
            tableLayoutPanel.Dock = DockStyle.Fill;
            tableLayoutPanel.ColumnCount = 2;
            tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
            tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
            tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
            tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50F));
            tableLayoutPanel.RowCount = 2;
            tableLayoutPanel.Controls.Add(label);
            tableLayoutPanel.SetColumn(label, 0);
            tableLayoutPanel.SetRow(label, 0);

            tableLayoutPanel.Controls.Add(label2);
            tableLayoutPanel.SetColumn(label2, 1);
            tableLayoutPanel.SetRow(label2, 1);

            this.label.DataBindings.Add(new Binding("Text", this.vm, "Text1", false));
            this.label2.DataBindings.Add(new Binding("Text", this.vm, "Text2", false));

            label.Font = new Font("MS Gothic", 40);
            label.Height = 50;
            label.Text = count.ToString();
            label.TextAlign = ContentAlignment.MiddleCenter;
            label.Dock = DockStyle.Fill;

            label2.Font = new Font("MS Gothic", 40);
            label2.Height = 50;
            label2.Text = count.ToString();
            label2.TextAlign = ContentAlignment.MiddleCenter;
            label2.Dock = DockStyle.Fill;

            timer.Interval = 100;
            timer.Tick += timer_Tick;
            timer.Start();
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            count++;
            this.vm.Text1 = count.ToString();
            this.vm.Text2 = (-count).ToString();
        }
    }
}
using System;
using System.ComponentModel;

namespace WindowsFormsApp1
{
    public class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private void RaisePropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        private string _text1 = "test";
        public string Text1
        {
            set
            {
                _text1 = value;
                RaisePropertyChanged("Text1");
            }
            get
            {
                return _text1;
            }
        }

        private string _text2 = "test";
        public string Text2
        {
            set
            {
                _text2 = value;
                RaisePropertyChanged("Text2");
            }
            get
            {
                return _text2;
            }
        }
    }
}

環境によってちらつきが発生する難しいバグ発生となった.
このバグが発生する場合には「コントロールのダブルバッファリングを有効にして、ちらつきを防止する」を実施しても関係ないようだ.
難しい.

≫ 続きを読む

2019/04/14 コンピュータ   TakeMe
タグ:Windows Forms